package nachos.filesys;

import java.util.ArrayList;

import nachos.machine.FileSystem;
import nachos.machine.Lib;
import nachos.machine.Machine;
import nachos.machine.OpenFile;
import nachos.machine.OpenFileWithPosition;

public class UFileSystem implements FileSystem  {

	/**
	 * create a UNIX-style file system
	 */
	public UFileSystem(){
		if(Machine.synchDisk()==null)
			if (debugFlag) System.out.println("sysch disk is null");
		else
			if (debugFlag) System.out.println("sysch disk is not null");

    	constructFileSysytem();
	}	
	
    /**
     * Atomically open a file, optionally creating it if it does not 
     * already exist. If the file does not
     * already exist and <tt>create</tt> is <tt>false</tt>, returns
     * <tt>null</tt>. If the file does not already exist and <tt>create</tt>
     * is <tt>true</tt>, creates the file with zero length. If the file already
     * exists, opens the file without changing it in any way.
     *
     * @param	name		the name of the file to open.
     * @param	create		<tt>true</tt> to create the file if it does not
     *				already exist.
     * @return	an <tt>OpenFile</tt> representing a new instance of the opened
     *		file, or <tt>null</tt> if the file could not be opened.
     */
	@Override
	public OpenFile open(String name, boolean create) {
		// according to the current unode number, we could get currentDirectory unode
		Unode curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
		
		// now we search the current directory, is file "name" in there?
		int unodeNum = curDirUnode.searchNameInIndexTable(name);		
		if (unodeNum != -1){//found!!?
			// if it's a directory, we cannot open it
			if (UnodeManager.getUnode(unodeNum).getFileType() == Unode.typeDirectory)
				return null;
			//create a UOpenFile object
			UOpenFile uOpenFile = new UOpenFile(unodeNum);
			if (create)
				UnodeManager.setUnodeModified(unodeNum);
			return uOpenFile;
		}
		else if(create){//need create!!
			//allocate a new Unode
			unodeNum = UnodeManager.allocateNewUnode(Unode.typeFile);
			
			//write file name and unode number into the current directory
			DataManager.getInstance().addDirectoryItem(
					currentDirectoryUnodeNum, name, unodeNum);
			
			//increase the current directory
			curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
			curDirUnode.increaseFileSize(254);
			UnodeManager.writeUnode(currentDirectoryUnodeNum, curDirUnode);
			
			
			//create a UOpenFile object
			UOpenFile uOpenFile = new UOpenFile(unodeNum);
			return uOpenFile;
		}else{
			return null;
		}
	}

	/**
	 * open a directory
	 * @param dirName the target directory we want to open
	 * @param create true for create a new directory when not found
	 * @return unode number of the target directory, or -1 for not found nor create
	 */
	public static int openDirectory(String dirName, boolean create){
		// according to the current unode number, we could get currentDirectory unode
		Unode curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
		
		// now we search the current directory, is file "name" in there?
		int unodeNum = curDirUnode.searchNameInIndexTable(dirName);
		if (unodeNum != -1){//found!!
			if (UnodeManager.getUnode(unodeNum).getFileType() != Unode.typeDirectory)
				return -1;
		}
		else if(create){//need create!!
			// allocate a new Unode
			unodeNum = UnodeManager.allocateNewUnode(Unode.typeDirectory);
			
			// allocate a new block and add it to the new unode's index table
			int newBlockNum = DataManager.getInstance().createDirectory(unodeNum, currentDirectoryUnodeNum);
			Unode unode = Unode.newUnode(Unode.typeDirectory);
			unode.addIndexItem(newBlockNum);
			UnodeManager.writeUnode(unodeNum, unode);
			
			//write file name and unode number into the current directory
			DataManager.getInstance().addDirectoryItem(
					currentDirectoryUnodeNum, dirName, unodeNum);
			
			//increase the current directory
			curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
			curDirUnode.increaseFileSize(254);
			UnodeManager.writeUnode(currentDirectoryUnodeNum, curDirUnode);		
			
		}
		else{
			return -1;
		}
		// change current directory to the new one
		currentDirectoryUnodeNum = unodeNum;
		
		if (dirName.equals("..")){
			if (!currentPath.isEmpty())
				currentPath.remove(currentPath.size()-1);
		}
		else if(!dirName.equals(".")){
			currentPath.add(dirName);
		}		
		
		return unodeNum;
		
	}

	/**
     * Atomically remove an existing file. After a file is removed, it cannot
     * be opened until it is created again with <tt>open</tt>. If the file is
     * already open, it is up to the implementation to decide whether the file
     * can still be accessed or if it is deleted immediately.
     *
     * @param	name	the name of the file to remove.
     * @return	<tt>true</tt> if the file was successfully removed.
     */
	@Override
	public boolean remove(String name) {
		// search for the file or directory "name" in the current directory
		// if exists: simply free the resources and the unode
		
		// according to the current unode number, we could get currentDirectory unode
		Unode curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
		
		// now we search the current directory, is file "name" in there?
		int unodeNum = curDirUnode.searchNameInIndexTable(name);		
		if (unodeNum == -1)
			return false;
		
		Unode unode = UnodeManager.getUnode(unodeNum);
		if (unode.getFileType() == Unode.typeFile){
			// delete self index item in the parent directory
			DataManager.getInstance().removeDirectoryItemByUnodeNum(currentDirectoryUnodeNum, name);
			DataManager.getInstance().printDirectoryInfoByUnodeNum(0);
			// Free the unode
			UnodeManager.setUnodeBitmapFree(unodeNum);
			// Free all the related resources
			int blockResourceCount = unode.getFileSize() / sBlockSize + 1;
			for (int i = 0; i < blockResourceCount; i ++){
				int phyBlockPos = unode.searchBlockInIndexTable(i);
				DataManager.getInstance().setDataBitmapFree(phyBlockPos);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * 
	 * @param dirName
	 * @return 0:success removed, 1:directory not empty, -1:cannot found 
	 */
	public static int removeDir(String dirName){
		// according to the current unode number, we could get currentDirectory unode
		Unode curDirUnode = UnodeManager.getUnode(currentDirectoryUnodeNum);
				
		// now we search the current directory, is file "name" in there?
		int unodeNum = curDirUnode.searchNameInIndexTable(dirName);		
		if (unodeNum == -1)
			return -1;
		
		Unode targetUnode = UnodeManager.getUnode(unodeNum); 
		if (targetUnode.getFileType() != Unode.typeDirectory)
			return -1;
		
		if (targetUnode.getFileSize() != 8){
			if (debugFlag) System.out.println("not empty!");
			return 1;
		}
		
		// delete self index item in the parent directory
		DataManager.getInstance().removeDirectoryItemByUnodeNum(currentDirectoryUnodeNum, dirName);

		// Free the unode
		UnodeManager.setUnodeBitmapFree(unodeNum);
		
		// Free the block
		int phyBlockPos = targetUnode.searchBlockInIndexTable(0);
		DataManager.getInstance().setDataBitmapFree(phyBlockPos);

		return 0;
	}


	private void constructFileSysytem(){    	
 
    	while(!checkFileSystem())
		{
    		createNewFileSystem();
    	}
    	
    	if (debugFlag){
    		selfTest();
    	}
    	
    }
	
	
	
	//list everything in the root directory
	private void selfTest(){
		System.out.println("=============== filesys test ===============");		

		// test create directory
		System.out.println("--------------------- create directory ---------------------");
		openDirectory("newDir", true);
		System.out.println("------------------- create directory end -------------------");
		
		// test remove directory
		System.out.println("---------------------- remove directory -------------------");
		removeDir("newDir");
		System.out.println("------------------- remove directory end -------------------");
		
		// test open
		System.out.println("--------------------- create file ---------------------");
		OpenFile of = open("new2", true);
		System.out.println("------------------- create file end -------------------");
				
		// test write
		System.out.println("--------------------- write ---------------------");
		String str = "hello world!";
		byte[] writeTemp = str.getBytes();
		of.seek(0);
		int writeLength = of.write(writeTemp, 0, writeTemp.length);
		System.out.println("write length = " + writeLength);
		System.out.println("------------------- write end -------------------");
		
		// test read
		System.out.println("--------------------- read ---------------------");
		byte[] readTemp = new byte[20];
		of.seek(0);
		int readLength = of.read(readTemp, 0, 20);
		String readContent = Lib.bytesToString(readTemp, 0, readLength);
		System.out.println("read content = " + readContent + "\tread length = " + readLength);
		System.out.println("------------------- read end -------------------");
		
		
		// test remove
		System.out.println("--------------------- remove ---------------------");
		remove("new");
		System.out.println("------------------- remove end -------------------");
		
		System.out.println("=============== filesys test end ===============");
		
	}
	
	/**
	 * create a new file system
	 */
	private void createNewFileSystem(){
		System.out.println("creating a new file system....");

		// super-block
		createSuperBlock();
		
		// data-bitmap
		createDataBitmapZone();
		
		// unode-bitmap
		createUnodeBitmapZone();
		
		// unode-zone
		createUnodeZone();
		
		// data-zone
		createDataZone();
		
		System.out.println("create filesystem successfully");
	}
	
	private boolean checkFileSystem(){
    	if (debugFlag){
    		System.out.println("check file system....");
    		System.out.println("\tread in the super block:");
    	}
    	
    	byte[] firstBlock = new byte[DiskHelper.getBlockSize()]; 
    	diskHelper.readBlock(0, 1, firstBlock);    	
    	
    	int magicNumber = this.extractInfoFromSuperBlock(firstBlock, cMagicNumberCode);    	
    	this.outputDebug("\t\tmagic number = " + magicNumber);
    	if (magicNumber != sMagicNumber)
    		return false;
    	
    	int blockSize = this.extractInfoFromSuperBlock(firstBlock, cBlockSizeCode);
    	this.outputDebug("\t\tblock size = " + blockSize);
    	if (blockSize != sBlockSize)
    		return false;
    	
    	int unodeSize = this.extractInfoFromSuperBlock(firstBlock, cUnodeSizeCode);
    	this.outputDebug("\t\tunode size = " + unodeSize);
    	if (unodeSize != sUnodeSize)
    		return false;
    	
    	int unodeNumber = this.extractInfoFromSuperBlock(firstBlock, cUnodeNumberCode);
    	this.outputDebug("\t\tunode number = " + unodeNumber);
    	Lib.assertTrue(unodeNumber > 0 && unodeNumber < sMaxUnodeNumber);
    	
    	int unodeZoneStartPosition = this.extractInfoFromSuperBlock(
    			firstBlock, cUnodeZoneStartPositionCode);
    	this.outputDebug("\t\tunode zone start position = " + unodeZoneStartPosition);
    	if (unodeZoneStartPosition != sUnodeZoneStartPosition)
    		return false;
    	
    	int dataZoneStartPosition = this.extractInfoFromSuperBlock(
    			firstBlock, cDataZoneStartPositionCode);
    	this.outputDebug("\t\tdata zone start position = " + dataZoneStartPosition);
    	if (dataZoneStartPosition != sDataZoneStartPosition)
    		return false;
    	
    	int rootDirectoryUnodeNumber = this.extractInfoFromSuperBlock(
    			firstBlock, cRootDirectoryUnodeNumCode);
    	this.outputDebug("\t\troot directory unode number = " + rootDirectoryUnodeNumber);
    	if (rootDirectoryUnodeNumber != sRootUnodeNumber)
    		return false;
    	
    	int dataBitmapZoneStartPosition = this.extractInfoFromSuperBlock(
    			firstBlock, cDataBitmapZoneStartPosCode);
    	this.outputDebug("\t\tdata bitmap zone start position = " + dataBitmapZoneStartPosition);
    	if (dataBitmapZoneStartPosition != sDataBitmapZoneStartPosition)
    		return false;
    	
    	int dataBitmapZoneBlockNum = this.extractInfoFromSuperBlock(
    			firstBlock, cDataBitmapZoneBlockNumCode);
    	this.outputDebug("\t\tdata bitmap zone block number = " + dataBitmapZoneBlockNum);
    	if (dataBitmapZoneBlockNum != sDataBitmapZoneBlockNumber)
    		return false;
    	
    	int unodeBitmapZoneStartPosition = this.extractInfoFromSuperBlock(
    			firstBlock, cUnodeBitmapZoneStartPosCode);
    	this.outputDebug("\t\tunode bitmap zone start position = " + unodeBitmapZoneStartPosition);
    	if (unodeBitmapZoneStartPosition != sUnodeBitmapZoneStartPosition)
    		return false;
    	
    	int unodeBitmapZoneBlockNum = this.extractInfoFromSuperBlock(
    			firstBlock, cUnodeBitmapZoneBlockNumCode);
    	this.outputDebug("\t\tunode bitmap zone block number = " + unodeBitmapZoneBlockNum);
    	if (unodeBitmapZoneBlockNum != sUnodeBitmapZoneBlockNumber)
    		return false;
    	
    	int dataZoneBlockNum = this.extractInfoFromSuperBlock(
    			firstBlock, cDataZoneBlockNumCode);
    	this.outputDebug("\t\tdata zone block number = " + dataZoneBlockNum);
    	if (dataZoneBlockNum != sDataZoneBlockNumber)
    		return false;
    	
    	int unodeZoneBlockNum = this.extractInfoFromSuperBlock(
    			firstBlock, cUnodeZoneBlockNumCode);
    	this.outputDebug("\t\tunode zone block number = " + unodeZoneBlockNum);
    	if (unodeZoneBlockNum != sUnodeZoneBlockNumber)
    		return false;
    	
    	return true;    	
	}
	
	private void createSuperBlock(){
		byte[] bSuperBlock = new byte[sBlockSize];		
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cMagicNumberCode, sMagicNumber);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cBlockSizeCode, sBlockSize);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeSizeCode, sUnodeSize);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeNumberCode, 1);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeZoneStartPositionCode, sUnodeZoneStartPosition);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cDataZoneStartPositionCode, sDataZoneStartPosition);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cRootDirectoryUnodeNumCode, sRootUnodeNumber);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cDataBitmapZoneStartPosCode, sDataBitmapZoneStartPosition);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cDataBitmapZoneBlockNumCode, sDataBitmapZoneBlockNumber);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeBitmapZoneStartPosCode, sUnodeBitmapZoneStartPosition);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeBitmapZoneBlockNumCode, sUnodeBitmapZoneBlockNumber);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cUnodeZoneBlockNumCode, sUnodeZoneBlockNumber);
		this.writeInfoIntoSuperBlock(bSuperBlock, 
				cDataZoneBlockNumCode, sDataZoneBlockNumber);
		diskHelper.writeBlock(0, 1, bSuperBlock);
	}
	
	private void createDataBitmapZone(){
		// block #0 in data zone is for the root directory
		byte[] bitMapBlock = new byte[sBlockSize];
		bitMapBlock[0] = (byte) 0x80;
		for (int j = 1; j < bitMapBlock.length; j ++)
			bitMapBlock[j] = 0;
		diskHelper.writeBlock(sDataBitmapZoneStartPosition, 1, bitMapBlock);
		
		for (int i = 1; i < sDataBitmapZoneBlockNumber; i ++){
			bitMapBlock = new byte[sBlockSize];
			for (int j = 0; j < bitMapBlock.length; j ++)
				bitMapBlock[j] = (byte) 0x00;
			diskHelper.writeBlock(sDataBitmapZoneStartPosition+i, 1, bitMapBlock);			
		}
	}
	
	private void createUnodeBitmapZone(){
		byte[] unodeBitmapBlock = new byte[sBlockSize];
		unodeBitmapBlock[0] = (byte) 0x80;//number 0 unode is for the root directory
		for (int i = 1; i < unodeBitmapBlock.length; i ++)
			unodeBitmapBlock[i] = (byte) 0x00;
		diskHelper.writeBlock(sUnodeBitmapZoneStartPosition, 1, unodeBitmapBlock);
		
	}
	
	private void createUnodeZone(){
		// create the #0 unode for the root directory		
		Unode rootUnode = UnodeManager.createRootUnode();
		UnodeManager.writeUnode(sRootUnodeNumber, rootUnode);
		
		byte[] temp = new byte[sBlockSize];
		
		// empty the rest areas
		temp = new byte[sBlockSize * (sUnodeZoneBlockNumber-1)];
		for (int i = 0; i < temp.length; i ++)
			temp[i] = 0;
		diskHelper.writeBlock(sUnodeZoneStartPosition+1, sUnodeZoneBlockNumber-1, temp);		
	}
	
	private void createDataZone(){
		// simply create root directory block
		DataManager.getInstance().createRootDirectory();
	}
	private void outputDebug(String content){
		if (debugFlag){
			System.out.println(content);
		}
	}
	
	static private final int 
		cMagicNumberCode = 0,
		cBlockSizeCode = 1,
		cUnodeSizeCode = 2,
		cUnodeNumberCode = 3,
		cDataBitmapZoneStartPosCode = 4,
		cDataBitmapZoneBlockNumCode = 5,
		cUnodeBitmapZoneStartPosCode = 6,
		cUnodeBitmapZoneBlockNumCode = 7,
		cUnodeZoneStartPositionCode = 8,
		cUnodeZoneBlockNumCode = 9,
		cDataZoneStartPositionCode = 10, 
		cDataZoneBlockNumCode = 11,
		cRootDirectoryUnodeNumCode = 12;
	/**
	 * get specific information from the super block
	 * @param superBlock the byte array represent the super block
	 * @param partCode code of the destination part
	 * @return result information
	 */
	private int extractInfoFromSuperBlock(byte[] superBlock, int partCode){
		int result = -1;
		byte[] temp = new byte[4];
		int startPos = findStartPos(partCode);		
		System.arraycopy(superBlock, startPos, temp, 0, 4);
		result = Lib.bytesToInt(temp, 0);
		return result;
	}
	
	/**
	 * write specific information into the super block
	 * @param superBlock the byte array represent the super block
	 * @param partCode code of the destination part
	 * @param value content to write in 
	 */
	private void writeInfoIntoSuperBlock(byte[] superBlock, int partCode, int value){
		byte[] temp = Lib.bytesFromInt(value);
		int startPos = findStartPos(partCode);		
		System.arraycopy(temp, 0, superBlock, startPos, 4);
		return;
	}
	
	private int findStartPos(int partCode){
		int startPos;
		switch (partCode){
		case cMagicNumberCode:
			startPos = 0;
			break;
		case cBlockSizeCode:
			startPos = 4;
			break;
		case cUnodeSizeCode:
			startPos = 8;
			break;
		case cUnodeNumberCode:
			startPos = 12;
			break;
		case cDataBitmapZoneStartPosCode:
			startPos = 16;
			break;
		case cDataBitmapZoneBlockNumCode:
			startPos = 20;
			break;
		case cUnodeBitmapZoneStartPosCode:
			startPos = 24;
			break;
		case cUnodeBitmapZoneBlockNumCode:
			startPos = 28;
			break;
		case cUnodeZoneStartPositionCode:
			startPos = 32;
			break;
		case cUnodeZoneBlockNumCode:
			startPos = 36;
			break;
		case cDataZoneStartPositionCode:
			startPos = 40;
			break;	
		case cDataZoneBlockNumCode:
			startPos = 44;
			break;
		case cRootDirectoryUnodeNumCode:
			startPos = 48;
			break;
		default:
			Lib.assertNotReached();
			startPos = -1;
		}
		return startPos;
	}
    
    /** DiskHelper */
    DiskHelper diskHelper = DiskHelper.getInstance();

    /** magic number */
    private static final int sMagicNumber = 112233;
    
    /** block size */
    private static final int sBlockSize = DiskHelper.getBlockSize();
    
    /** unode size */
    private static final int sUnodeSize = 128;
    
    /** unode max number */
    private static final int sMaxUnodeNumber = 1024;
    
    /** start position of the unode-zone */
    private static final int sUnodeZoneStartPosition = 130;
    
    private static final int sUnodeZoneBlockNumber = 128;
    
    /** start position of the data-zone */
    public static final int sDataZoneStartPosition = 258;
    
    public static final int sDataZoneBlockNumber = 1024 * 1024 - 258;    
    
    /** unode number of the root directory */
    private static final int sRootUnodeNumber = 0;
    
    /** data bitmap zone start position*/
    public static final int sDataBitmapZoneStartPosition = 1;
    
    public static final int sDataBitmapZoneBlockNumber = 128;
    
    /** unode bitmap zone start position */
    private static final int sUnodeBitmapZoneStartPosition = 129;
   
    private static final int sUnodeBitmapZoneBlockNumber = 1;

    
    /** debug trigger*/
    private static final boolean debugFlag = false;
    
    /** 
     * the absolute path of the current process
     * this String[] array should be refreshed every time processes switch.
     * 
     * */
    public static ArrayList<String> currentPath = new ArrayList<String>();
    
    /**
     * the unode number of the current Process.
     * this variable should also be refreshed every time processes switch.
     */
    public static int currentDirectoryUnodeNum = sRootUnodeNumber;

	public static boolean format = false;
	
	private class UOpenFile extends OpenFileWithPosition {
		UOpenFile(int _unodeNum){
			unode = UnodeManager.getUnode(_unodeNum);
			open = true;
			unodeNum = _unodeNum;
		}
		
		 /**
	     * Read this file starting at the specified position and return the number
	     * of bytes successfully read. If no bytes were read because of a fatal
	     * error, returns -1
	     *
	     * @param	pos	the offset in the file at which to start reading.
	     * @param	buf	the buffer to store the bytes in.
	     * @param	offset	the offset in the buffer to start storing bytes.
	     * @param	length	the number of bytes to read.
	     * @return	the actual number of bytes successfully read, or -1 on failure.
	     */
		@Override
		public int read(int pos, byte[] buf, int offset, int length) {
			if (debugFlag)
				unode.printInfo("read : unode#" + unodeNum);
			if (!open)
				return -1;
			
			if (length == 0)
				return 0;
			
			// the start position must be less than the file size
			if (debugFlag)
				System.out.println("read : pos = " + pos);
			Lib.assertTrue(pos <= unode.getFileSize());
			
			// assume that we read until this file ends
			int endPos = Math.min(pos + length, unode.getFileSize());
			length = endPos - pos;
			
			int totalReadLength = 0;
			int readBlockCount = 0;
			for (int i = (pos / sBlockSize) * sBlockSize; i < pos + length; i += sBlockSize)
				readBlockCount ++;
			
			int virBlockStartPos = pos / sBlockSize;
			int virBlockPos, phyBlockPos;
			byte[] temp = new byte[sBlockSize];
			int tempPointer, bufPointer;
			int tempReadLength;
			bufPointer = offset;
			
			// read first virtual block
			virBlockPos = virBlockStartPos;
			phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
			diskHelper.readBlock(phyBlockPos, 1, temp);
			tempReadLength = Math.min(sBlockSize - pos % sBlockSize, length);
			tempPointer = pos % sBlockSize;
			System.arraycopy(temp, tempPointer, buf, bufPointer, tempReadLength);
			bufPointer += tempReadLength;//increase the buf pointer 
			totalReadLength += tempReadLength;
			if (debugFlag) System.out.println("read : \n\tvirBlockPos = " + virBlockPos + "\n\tphyBLockPos = " + phyBlockPos);
			if (totalReadLength >= length){
				return totalReadLength;
			}
			
			// read blocks in the middle
			for (int i = 1; i < readBlockCount-1; i++){
				virBlockPos = virBlockStartPos + i;
				phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
				diskHelper.readBlock(phyBlockPos, 1, temp);
				System.arraycopy(temp, 0, buf, bufPointer, sBlockSize);
				bufPointer += sBlockSize;
				totalReadLength += sBlockSize;
			}
			if (totalReadLength >= length){
				return totalReadLength;
			}
			
			// read the last block
			virBlockPos = virBlockStartPos + readBlockCount - 1;
			phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
			diskHelper.readBlock(phyBlockPos, 1, temp);
			tempReadLength = (pos + length) % sBlockSize;
			System.arraycopy(temp, 0, buf, bufPointer, tempReadLength);
			totalReadLength += tempReadLength;
			
			return totalReadLength;
		}
		
		
		 /**
	     * Write this file starting at the specified position and return the number
	     * of bytes successfully written. If no bytes were written because of a
	     * fatal error, returns -1.
	     *
	     * @param	pos	the offset in the file at which to start writing.
	     * @param	buf	the buffer to get the bytes from.
	     * @param	offset	the offset in the buffer to start getting.
	     * @param	length	the number of bytes to write.
	     * @return	the actual number of bytes successfully written, or -1 on
	     *		failure.
	     */
		@Override
		public int write(int pos, byte[] buf, int offset, int length) {
			if (debugFlag)
				unode.printInfo("write : unode#" + unodeNum);
			if (!open)
				return -1;
			int endPos = Math.max(unode.getFileSize(), pos + length);
			if (debugFlag) System.out.println("endPos = " + endPos + "\tfile size = " + unode.getFileSize());
			
			// If this write operation will increase the file, 
			// allocate some blocks for this file first.
			if (endPos > unode.getFileSize()){
				int previousBlockCount = unode.getFileSize() == 0 ? 0 : (unode.getFileSize() / sBlockSize + 1);
				int addBlockCount = 0;
				for (int i = previousBlockCount * sBlockSize;
						i < endPos; i += sBlockSize)
					addBlockCount ++;
				if (debugFlag) System.out.println("in write : addBlockCount = " + addBlockCount);
				ArrayList<Integer> addBlockNumList = DataManager.getInstance().requestFreeBlock(addBlockCount);
				for (int i = 0; i < addBlockNumList.size(); i ++){
					if (debugFlag) System.out.println("get new block#" + addBlockNumList.get(i));
					unode.addIndexItem(addBlockNumList.get(i));
					unode.increaseFileSize(sBlockSize);
				}
				// refresh the file size
				unode.setFileSize(endPos);
				UnodeManager.writeUnode(unodeNum, unode);
			}
			
			// Now we start write the contents into the file.
			if (debugFlag)
				unode.printInfo("write : unode#" + unodeNum);
			int totalWriteLength = 0;
			int writeBlockCount = 0;
			for (int i = (pos / sBlockSize) * sBlockSize; i < pos + length; i += sBlockSize)
				writeBlockCount ++;
			
			int virBlockStartPos = pos / sBlockSize;
			int virBlockPos, phyBlockPos;
			byte[] temp = new byte[sBlockSize];
			int tempPointer, bufPointer;
			int tempWriteLength;
			bufPointer = offset;
			
			// write first virtual block
			virBlockPos = virBlockStartPos;
			phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
			
			if (debugFlag) System.out.println("write : \n\tvirBlockPos = " + virBlockPos + "\n\tphyBLockPos = " + phyBlockPos);
			
			diskHelper.readBlock(phyBlockPos, 1, temp);
			tempWriteLength = Math.min(sBlockSize - (pos % sBlockSize), length);
			tempPointer = pos % sBlockSize;
			System.arraycopy(buf, bufPointer, temp, tempPointer, tempWriteLength);
			diskHelper.writeBlock(phyBlockPos, 1, temp);
			bufPointer += tempWriteLength;
			totalWriteLength += tempWriteLength;
			if (totalWriteLength >= length){
				UnodeManager.setUnodeModified(unodeNum);
				return totalWriteLength;
			}
			
			// rwrite blocks in the middle
			for (int i = 1; i < writeBlockCount-1; i++){
				virBlockPos = virBlockStartPos + i;
				phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
				System.arraycopy(buf, bufPointer, temp, 0, sBlockSize);
				if (debugFlag) System.out.println("write : \n\tvirBlockPos = " + virBlockPos + "\n\tphyBLockPos = " + phyBlockPos);
				diskHelper.writeBlock(phyBlockPos, 1, temp);
				bufPointer += sBlockSize;
				totalWriteLength += sBlockSize;
			}
			if (totalWriteLength >= length){
				UnodeManager.setUnodeModified(unodeNum);
				return totalWriteLength;
			}
				
			// write the last block
			virBlockPos = virBlockStartPos + writeBlockCount - 1;
			phyBlockPos = unode.searchBlockInIndexTable(virBlockPos);
			diskHelper.readBlock(phyBlockPos, 1, temp);
			tempWriteLength = endPos % sBlockSize;
			System.arraycopy(buf, bufPointer, temp, 0, tempWriteLength);
			if (debugFlag) System.out.println("write : \n\tvirBlockPos = " + virBlockPos + "\n\tphyBLockPos = " + phyBlockPos);
			diskHelper.writeBlock(phyBlockPos, 1, temp);			
			totalWriteLength += tempWriteLength;
			
			UnodeManager.setUnodeModified(unodeNum);
			return totalWriteLength;
		}
		
		
	    /**
	     * Get the length of this file.
	     *
	     * @return	the length of this file, or -1 if this file has no length.
	     */
		@Override
		public int length() {
			return unode.getFileSize();
		}
		
		
		/**
	     * Close this file and release any associated system resources.
	     */
		@Override
		public void close() {
			open = false;
		}
		
		Unode unode = null;
		int unodeNum = -1;
		boolean open = false;
	}
	
	/**
	 * This UnodeManager class responsible for all disk operantions about Unode, such like:
	 * 		allocate a new Unode from disk 
	 * 		read or transfer between Unode and byte array
	 * 		arrange the Unode Bitmap Zone 
	 * 		etc.
	 * 
	 * @author sylvia
	 */
	public static class UnodeManager{

		public static Unode createRootUnode() {
			rootUnode = Unode.newUnode(Unode.typeDirectory);
			rootUnode.addIndexItem(sDataZoneStartPosition);
			return rootUnode;
		}

		/**
		 * Get specific Unode object according to the unode number.
		 * @param unodeNum
		 * @return
		 */
		public static Unode getUnode(int unodeNum){
			byte[] temp = getUnodeByteArray(unodeNum);
			return Unode.byteToUnode(temp);
		}

		/**
		 * Find the root unode object
		 * @return the root unode
		 */
		public static Unode getRootUnode(){
			rootUnode = getUnode(sRootUnodeNumber);
			return rootUnode;
		}

		
		/**
		 * get the byte array of the specific unode number
		 * @param unodeNum
		 * @return
		 */
		private static byte[] getUnodeByteArray(int unodeNum){
			int unodePerBlock = sBlockSize / sUnodeSize;//8
			int blockNum = sUnodeZoneStartPosition + unodeNum / unodePerBlock;
			byte[] temp = new byte[sBlockSize];
			diskHelper.readBlock(blockNum, 1, temp);
			byte[] result = new byte[sUnodeSize];
			System.arraycopy(temp, sUnodeSize * (unodeNum % unodePerBlock), 
					result, 0, sUnodeSize);
			return result;
		}
		
		/**
		 * allocate and create a new Unode object
		 */
		public static int allocateNewUnode(int type){			
			// search in the UnodeBitmapZone, find a free position
			int unodePos = getFreeUnodePos();
			if (debugFlag) System.out.println("UnodeManager.allocateNewUnode : unodePos = " + unodePos + " type = " + type);
			
			// set the bit position used
			setUnodeBitmapUsed(unodePos);
			
			
			// write the new Unode into the disk
			Unode unode = Unode.newUnode(type);
			writeUnode(unodePos, unode);
			return unodePos;
			
		}
		
		private static void setUnodeBitmapUsed(int unodePos){
			setUnodeBitmap(unodePos, true);
		}
		
		private static void setUnodeBitmapFree(int unodePos){
			setUnodeBitmap(unodePos, false);
		}
		
		private static void setUnodeBitmap(int unodeNum, boolean value){
			// calculate the block number in the bitmap zone
			int targetBlockNumber = sUnodeBitmapZoneStartPosition;
			int targetByteNumber = unodeNum / 8;
			int targetBitNumber = unodeNum % 8;
			
			// read in the target block
			byte[] temp = new byte[sBlockSize]; 
			diskHelper.readBlock(targetBlockNumber, 1, temp);
			
			// set the target position
			if (value){
				byte b = (byte) (1 << (7-targetBitNumber));
				temp[targetByteNumber] = (byte) (temp[targetByteNumber] | b);
			}
			else{
				byte b = (byte) ((byte)0xff ^ (byte) (1 << (7-targetBitNumber)));
				temp[targetByteNumber] = (byte) (temp[targetByteNumber] & b);
			}
			
			// write back the target block
			diskHelper.writeBlock(targetBlockNumber, 1, temp);
		}
		
		public static void setUnodeModified(int unodeNum){
			Unode unode = getUnode(unodeNum);
			unode.updateModifyTime();
			writeUnode(unodeNum, unode);
		}
		
		public static void writeUnode(int unodeNum, Unode unode){
			byte[] temp = new byte[sBlockSize];
			byte[] bArray = Unode.byteFromUnode(unode);
			

			// read in the target block
			int targetBlockNumber = sUnodeZoneStartPosition + unodeNum / (sBlockSize / sUnodeSize);
			int tempPointer = (unodeNum % (sBlockSize / sUnodeSize)) * sUnodeSize;
			diskHelper.readBlock(targetBlockNumber, 1, temp);
			
			// add the unode to the target block array
			System.arraycopy(bArray, 0, temp, tempPointer, sUnodeSize);			
			
			// write back the target block
			diskHelper.writeBlock(targetBlockNumber, 1, temp);
		}
		
		/**
		 * Search the unode bitmap zone to get a free unode position.
		 * @return free unode position, relavant
		 */
		private static int getFreeUnodePos(){
			int unodePos = -1, bytePos = -1;
			boolean found = false;
			byte t = (byte) 0xff;
			
			byte[] temp = new byte[sBlockSize];
			diskHelper.readBlock(sUnodeBitmapZoneStartPosition, 1, temp);
			for (int i = 0; i < temp.length; i ++){
				//FREE!!
				if (temp[i] != (byte) 0xff){
					found = true;
					bytePos = i;
					t = temp[i];
					break;
				}
			}
			
			Lib.assertTrue(found, "no free unode position!");
			unodePos = bytePos * 8 + getFreeBitPos(t);
			
			return unodePos;
		}
		
		/**
		 * get free bit pos in a byte, this byte t must has a free bit. 
		 * @param t
		 * @return the first fee bit in t, started from left side.
		 */
		private static int getFreeBitPos(byte t){
			Lib.assertTrue(t != (byte) 0xff);
			for (int i = 0; i < 8; i ++){
				if (((1 << (7-i)) & t) == (byte) 0x00)
					return i;
			}
			Lib.assertNotReached();
			return -1;
		}

	    /** root unode */
	    private static Unode rootUnode = null;
	    
	    /** DiskHelper */
	    private static DiskHelper diskHelper = DiskHelper.getInstance();
	}
    
}
